﻿    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Prism.UnityExtensions;
using System.Windows;
using Microsoft.Practices.Unity;
using YasharEl.Infrastructure.ObjectFactory.Unity;
using System.Diagnostics.Contracts;
using Microsoft.Practices.Prism.Logging;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.PresentationModel.Shell;
using YasharEl.Infrastructure.PresentationModel.PrismIntegration.Shell;
using Microsoft.Practices.Prism.Events;
using YasharEl.Infrastructure.PresentationModel.PrismIntegration.Services;
using System.Reflection;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.Regions.Behaviors;
using YasharEl.Infrastructure.PresentationModel.PrismIntegration.Modularity;
using System.Windows.Controls;
using YasharEl.Infrastructure.PresentationModel.PrismIntegration.Modularity.RegionAdapters;
using System.Windows.Threading;
using Microsoft.Practices.Prism.Modularity;
using System.IO;

namespace YasharEl.Infrastructure.PresentationModel.PrismIntegration.Bootstrap
{
    internal class PrismUnityBootstrapper : UnityBootstrapper
    {
        #region Fields

        private PrismLogger prismLogger;

        #endregion

        #region ReadOnly Fields

        private readonly UnityDependencyContainer unityDependencyContainer;

        #endregion

        #region Constructors

        public PrismUnityBootstrapper(UnityDependencyContainer unityDependencyContainer)
        {
            Contract.Requires<ArgumentNullException>(unityDependencyContainer != null, "unityDependencyContainer could not be null.");

            this.unityDependencyContainer = unityDependencyContainer;
        }

        #endregion

        #region UnityBootstrapper Abstract Overrides

        protected override DependencyObject CreateShell()
        {
            var shell = Container.TryResolve<IShell>();
            if (shell == null)
            {
                ShellView shellView = new ShellView(
                        Container.Resolve<IEventAggregator>(),
                        Container.Resolve<IViewService>()
                    );
                prismLogger.Log("Shell not registered , therefore basic infrastructure ShellView used.", Category.Warn, Priority.Medium);

                /* Retrieve viewmodel from view and register it. */
                shell = (IShell)shellView.ViewModel;
                Container.RegisterInstance<IShell>(shell);
                Container.RegisterInstance<IMainWindow>(shellView);

                return shellView;
            }

            var mainWindow = Container.TryResolve<IMainWindow>();
            if (mainWindow == null)
            {
                mainWindow = shell as IMainWindow;
                if (mainWindow != null)
                {
                    /* Required by independent services. E.g. FileService. */
                    Container.RegisterInstance<IMainWindow>(mainWindow);
                    return (DependencyObject)mainWindow;
                }
            }

            throw new InvalidSystemConfigurationException("IMainWindow is not registered with the IOC container.");

        }

        #endregion

        #region UnityBootstrapper Overrides

        protected override IUnityContainer CreateContainer()
        {
            IUnityContainer resolvedContainer = unityDependencyContainer.DependencyResolver.Resolve<IUnityContainer>();

            return resolvedContainer;
        }

        protected override void InitializeShell()
        {
            Application.Current.MainWindow = (Window)Shell;
            Application.Current.MainWindow.Show();
        }

        protected override ILoggerFacade CreateLogger()
        {
            prismLogger = new PrismLogger(unityDependencyContainer.DependencyResolver.Resolve<ILogger<PrismUnityBootstrapper>>());
            return prismLogger;
        }

        protected override void ConfigureContainer()
        {
            // By registering the UI thread dispatcher  
            // we are able to invoke controls from anywhere.
            RegisterInstanceIfMissing<Dispatcher>(Dispatcher.CurrentDispatcher);

            // Register module manager with fault tolerance logic as singleton.
            RegisterTypeIfMissing<IFaultTolerantModuleManager, DefaultFaultTolerantModuleManager>(true);

            // Register the module load error strategy 
            // so that if a module doesn't load properly it will be excluded.
            RegisterTypeIfMissing<IModuleLoadErrorStrategy, ModuleLoadErrorStrategy>(false);
            // We use a custom initializer so that we can handle load errors.
            RegisterTypeIfMissing<IModuleInitializer, CustomModuleInitializer>(false);

            // IViewService will hide and show visual elements depending on workspace content.
            RegisterTypeIfMissing<IViewService, ViewService>(true);

            base.ConfigureContainer();

            var regionBehaviorFactory = Container.TryResolve<IRegionBehaviorFactory>();
            if (regionBehaviorFactory != null)
            {
                regionBehaviorFactory.AddIfMissing(RegionManagerRegistrationBehavior.BehaviorKey,
                    typeof(CustomRegionManagerRegistrationBehavior));
            }
        }

        protected override IModuleCatalog CreateModuleCatalog()
        {
            IModuleCatalog catalog = null;
            try
            {
                catalog = unityDependencyContainer.DependencyResolver.Resolve<IModuleCatalog>();
            }
            catch
            {
                if (catalog == null)
                {
                    var temp = new CustomModuleCatalog(unityDependencyContainer.DependencyResolver)
                    {
                        ModulePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)
                    };
                    catalog = temp;
                }
            }

            return catalog;
        }


        protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
        {
            var mappings = base.ConfigureRegionAdapterMappings() ?? Container.Resolve<RegionAdapterMappings>();
            mappings.RegisterMapping(typeof(ToolBarTray), Container.Resolve<ToolBarTrayRegionAdapter>());
            mappings.RegisterMapping(typeof(Menu), Container.Resolve<CustomItemsControlRegionAdapter>());
            mappings.RegisterMapping(typeof(MenuItem), Container.Resolve<CustomItemsControlRegionAdapter>());
            mappings.RegisterMapping(typeof(Panel), Container.Resolve<PanelRegionAdapter>());
            var windowRegionAdapter = Container.Resolve<WindowRegionAdapter>();
            windowRegionAdapter.WindowStyle = (Style)Application.Current.FindResource("ChildWindowView");
            mappings.RegisterMapping(typeof(Window), windowRegionAdapter);
            return mappings;
        }

        #endregion

        #region Private Methods

        void RegisterInstanceIfMissing<TFrom>(TFrom instance)
        {
            if (!Container.IsTypeRegistered(typeof(TFrom)))
            {
                Container.RegisterInstance<TFrom>(instance);
                prismLogger.Log(
                    string.Format("Instance of type {0} not registered yet. Bootstrapper register instance of type {1}.", 
                        typeof(TFrom), instance.GetType().FullNameToString()),
                    Category.Debug, Priority.Medium);
            }
        }

        void RegisterTypeIfMissing<TFrom, TTo>(bool registerAsSingleton) where TTo : TFrom
        {
            if (!Container.IsTypeRegistered(typeof(TFrom)))
            {
                if (registerAsSingleton)
                {
                    Container.RegisterType<TFrom, TTo>(new ContainerControlledLifetimeManager());
                }
                else
                {
                    Container.RegisterType<TFrom, TTo>();
                }

                prismLogger.Log(
                    string.Format("type {0} not registered, using {1} instead.", typeof(TFrom), typeof(TTo)),
                    Category.Debug, Priority.Medium);
            }
        }

        #endregion
    }
}
